package com.space.components.token;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.space.components.exception.Assert;
import com.space.components.exception.BizException;

import java.util.Calendar;
import java.util.Map;

/**
 * @author <a href="mailto:414563035@qq.com">Bedroom</a>
 * @date 2021/5/15 4:44 下午
 */
public class JWTUtil {

    /**
     * 签名 此签名为 space 的16位 大写 MD5
     */
    private static final String SIGN_KEY = "BE3D20E46CC69EFB";

    /**
     * 默认的过期时间，7天
     */
    private static final Integer DEFAULT_EXPIRES = 60 * 60 * 24 * 7;

    /**
     * token默认的长度
     */
    private static final Integer DEFAULT_TOKEN_SIZE = 3;

    /**
     * 数值只能为2^n(2的n次方)
     */
    private static final Integer OFFSETS_COEFFICIENT = Double.valueOf(Math.pow(2, 2)).intValue();

    /**
     * 生成令牌
     *
     * @param payload 数据正文
     * @param expires 过期时间，单位(秒)
     */
    public static String getToken(Map<String, String> payload, Integer expires) {
        //创建日历
        Calendar instance = Calendar.getInstance();
        //设置过期时间
        instance.add(Calendar.SECOND, expires);
        //创建jwt builder对象
        JWTCreator.Builder builder = JWT.create();
        //payload
        payload.forEach(builder::withClaim);
        //指定过期时间
        String token = builder
                .withExpiresAt(instance.getTime())
                //设置加密方式
                .sign(Algorithm.HMAC256(SIGN_KEY));
        //返回tokean
        return confoundPayload(token);
    }

    /**
     * 解析token
     *
     * @param token 输入混淆payload后的token
     */
    public static DecodedJWT verify(String token) {
        //如果token无效
        Assert.notEmpty(token, "无效的token");

        //解析token
        String dToken = deConfoundPayload(token);
        //创建返回结果
        return JWT.require(Algorithm.HMAC256(SIGN_KEY)).build().verify(dToken);

    }

    /**
     * 重载getToken 此方法为获取默认30分钟有效期的token
     *
     * @param map 数据正文
     */
    public static String getToken(Map<String, String> map) {
        return getToken(map, DEFAULT_EXPIRES);
    }


    /**
     * 对一个base64编码进行混淆  此处还可以进行replace混淆，考虑到效率问题，这里就不做啦~
     * 对于加密的思路还有位移、字符替换等~
     *
     * @param token 混淆payload前的token
     */
    private static String confoundPayload(String token) {
        //分割token
        String[] split = token.split("\\.");
        //如果token不符合规范
        if (split.length != DEFAULT_TOKEN_SIZE) {
            throw new BizException("签名不正确");
        }
        //取出payload
        String payload = split[1];
        //混淆处理后的token
        return split[0] + "." + reversePayload(payload, getOffsetsIndex(payload.length())) + "." + split[2];
    }

    /**
     * 对一个混淆后的base编码进行解析
     *
     * @param token 混淆后的token
     */
    private static String deConfoundPayload(String token) {
        //分割token
        String[] split = token.split("\\.");
        //如果token不符合规范
        if (split.length != DEFAULT_TOKEN_SIZE) {
            throw new BizException("签名不正确");
        }
        //取出payload
        String payload = split[1];

        //返回解析后的token
        return split[0] + "." + reversePayload(payload, payload.length() - getOffsetsIndex(payload.length()))+ "." + split[2];
    }

    /**
     * 将md5编码位移
     *
     * @param payload payload编码
     * @param index   位移处
     */
    private static String reversePayload(String payload, Integer index) {
        return payload.substring(index) + payload.substring(0, index);
    }

    private static int getOffsetsIndex(int payloadLength) {
        //获取长度
        int length = payloadLength / OFFSETS_COEFFICIENT;
        //指定截取点
        return length + (payloadLength & (OFFSETS_COEFFICIENT - 1));
    }
}